package com.example.lock.aspect;

import com.alibaba.fastjson2.JSON;
import com.example.lock.annotation.ConcurrentLock;
import com.example.lock.util.RedissonLockUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSONWriter.Feature;
import java.lang.reflect.Method;

/**
 * <p>Description: </p>
 *
 * @author zg
 * @version v1.0.0
 * @since 2024/8/21
 **/
@RequiredArgsConstructor
@Slf4j
@Aspect
@Component
public class ConcurrentLockAspect {

    private final RedissonLockUtils redissonLockUtils;

    @Around("@annotation(concurrentLock)")
    public Object around(ProceedingJoinPoint joinPoint, ConcurrentLock concurrentLock) throws Throwable {
        Object result = null;
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        String methodName = method.getName(); //joinPoint.getSignature().getName();
        String[] parameterNames = new StandardReflectionParameterNameDiscoverer().getParameterNames(method);

        log.info("ConcurrentLockAspect.around() start ->>>>>>>> method:{},is called on '{}', with args: {}, parameterNames: {}", methodName, joinPoint.getThis(), JSON.toJSONString(args), JSON.toJSONString(parameterNames));
        RLock lock = null;
        try {
            String lockKey = generateKey(joinPoint, concurrentLock);
            log.info("ConcurrentLockAspect.around(), lockKey={}", lockKey);
            if (concurrentLock.waitingTime() > 0){
                lock = redissonLockUtils.tryLock(lockKey, concurrentLock.waitingTime(), concurrentLock.leaseTime(), concurrentLock.isFairLock());
            } else {
                lock = redissonLockUtils.lock(lockKey, concurrentLock.leaseTime(), concurrentLock.isFairLock());
            }
            result = joinPoint.proceed();
            log.info("返回通知方法-目标方法名{}，返回结果为：", methodName, JSON.toJSONString(result, Feature.NullAsDefaultValue));
        } catch (Throwable e) {
            log.error("通知方法异常-目标方法名{}，", methodName, e);
            throw e;
        } finally {
            redissonLockUtils.unlock(lock);
        }
        log.info("ConcurrentLockAspect.around() end ->>>>>>>> method:{},is called on '{}', with args: {}, return: {}", methodName, joinPoint.getThis(), JSON.toJSONString(args), JSON.toJSONString(result) );
        return result;
    }

    private String generateKey(ProceedingJoinPoint joinPoint, ConcurrentLock concurrentLock) {
        String classPath = joinPoint.getThis().getClass().getPackage().getName();
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(method, method, args, new DefaultParameterNameDiscoverer());
        ExpressionParser parser = new SpelExpressionParser();

        StringBuilder keyBuilder = new StringBuilder(classPath).append(":").append(method.getName()).append(":");
        String[] keyNames = concurrentLock.values();
        for (int i = 0; i < keyNames.length; i++) {
            Expression exp = parser.parseExpression(keyNames[i]);
            String value = exp.getValue(context, String.class);
            keyBuilder.append(value);
            if(i != keyNames.length - 1){
                keyBuilder.append("&");
            }
        }
        return concurrentLock.key() +":"+ keyBuilder;
    }


    /*@Before("@annotation(concurrentLock)")
    public void before(JoinPoint point, ConcurrentLock concurrentLock){
        String methodName = point.getSignature().getName();
        Object[] args = point.getArgs();
        log.info("ConcurrentLockAspect.before() ->>>>>>>> method:{},is called on '{}', with args: {}", methodName, point.getThis(), JSON.toJSONString(args) );
    }*/

    /*@AfterReturning(value = "@annotation(concurrentLock)", returning = "returnObj")
    public void afterReturning(JoinPoint point, ConcurrentLock concurrentLock, Object returnObj) {
        String methodName = point.getSignature().getName();
        log.info("ConcurrentLockAspect.after() -<<<<<<<<< method:{}, returns: {} ", methodName, JSON.toJSONString(returnObj));
    }*/

}
